Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] Add circuit breaker to the migration guide #1764

Conversation

peter-csala
Copy link
Contributor

@peter-csala peter-csala commented Oct 31, 2023

Pull Request

The issue or feature being addressed

Details on the issue fix or feature implementation

  • Created a Migration.CircuitBreaker.cs file with V7 and V8 samples
  • Added a Migrating circuit breaker policies section to the migration-v8.md files

Open questions

  • Does it make sense to have a "standard"/"classic" section under the V7 paragraph?
    • Or shall I delete it since it is not supported in V8?
    • Personally I would vote to keep it to make it explicit that the "classic" version support is discontinued

Answer: keep it under the V7 paragraph

  • In the github wiki under the Circuit breaker we have a "suggest" which does not work for V8
    • Wiki link
    • Several weeks ago I've detailed the problem from V8 perspective on StackOverflow
    • What do want to do with this?
      • Shall I add a new anti-pattern under the Circuit Breaker page?
      • Shall I add a note at the end of this circuit breaker migration paragraph?
      • Other?

Answer: add it to the anti-patterns section and add reference to that from the migration guide

Confirm the following

  • I started this PR by branching from the head of the default branch
  • I have targeted the PR to merge into the default branch
  • I have included unit tests for the issue/feature
  • I have successfully run a local build

Copy link

codecov bot commented Oct 31, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (4d2dd5a) 84.53% compared to head (bf73059) 84.53%.
Report is 2 commits behind head on main.

❗ Current head bf73059 differs from pull request most recent head 511da8f. Consider uploading reports for the commit 511da8f to get more accurate results

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1764   +/-   ##
=======================================
  Coverage   84.53%   84.53%           
=======================================
  Files         307      307           
  Lines        6777     6777           
  Branches     1043     1043           
=======================================
  Hits         5729     5729           
  Misses        839      839           
  Partials      209      209           
Flag Coverage Δ
linux ?
macos 84.53% <ø> (ø)
windows 84.53% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@martincostello martincostello added this to the v8.2.0 milestone Oct 31, 2023
@martintmk
Copy link
Contributor

Does it make sense to have a "standard"/"classic" section under the V7 paragraph?

I think we should keep it for the reasons you mentioned - note that v8 does not have it anymore.

Shall I add a note at the end of this circuit breaker migration paragraph?

I think this is fine, another alternative is to use the low-allocation ExecuteOutcomeAsync API to avoid throwing exception. In that case caller can just execute the callback that will fail very quickly with minimum overhead.

src/Snippets/Docs/Migration.CircuitBreaker.cs Outdated Show resolved Hide resolved
@peter-csala
Copy link
Contributor Author

I think this is fine, another alternative is to use the low-allocation ExecuteOutcomeAsync API to avoid throwing exception. In that case caller can just execute the callback that will fail very quickly with minimum overhead.

I think it might make sense to mention this case on the migration guide and provide a link to the related anti-pattern section where we detail the solution. Do you agree?

@peter-csala
Copy link
Contributor Author

I think this is fine, another alternative is to use the low-allocation ExecuteOutcomeAsync API to avoid throwing exception. In that case caller can just execute the callback that will fail very quickly with minimum overhead.

I try to put together the suggested way but it feels a bit clumsy for me.

The anti-pattern

var stateProvider = new CircuitBreakerStateProvider();
var circuitBreaker = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(new()
    {
        ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
        BreakDuration = TimeSpan.FromSeconds(0.5),
        StateProvider = stateProvider
    })
    .Build();

if (stateProvider.CircuitState
    is not CircuitState.Open
    and not CircuitState.Isolated)
{
    await circuitBreaker.ExecuteAsync(static ct =>
    {
        // Your code goes here
        return default;
    }, CancellationToken.None);
}

The pattern

var context = ResilienceContextPool.Shared.Get();
var circuitBreaker = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(new()
    {
        ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
        BreakDuration = TimeSpan.FromSeconds(0.5),
    })
    .Build();

Outcome<bool> result = await circuitBreaker.ExecuteOutcomeAsync(static (ctx, state) =>
{
    // Your code goes here

    return Outcome.FromResultAsValueTask(true);
}, context, "state");

ResilienceContextPool.Shared.Return(context);

Using bool as a TResult feels a bit odd. Even if I replace that to object? still does not feel good

Outcome<object?> result = await circuitBreaker.ExecuteOutcomeAsync(static (ctx, state) =>
{
    // Your code goes here

    return Outcome.FromResultAsValueTask<object?>(null);
}, context, "state");

@martintmk Do you have any other alternative which feels more natural?

@martintmk
Copy link
Contributor

@peter-csala

The main idea is to just execute the callback as one normally would and afterwards one can check the result. For example:

Outcome<bool> result = await circuitBreaker.ExecuteOutcomeAsync(static (ctx, state) => ... );

if (result.Exception is BrokenCircuitException)
{
   // the execution was stopped by CB
}
else
{
   // the callback was executed and passed through CB
}

@peter-csala
Copy link
Contributor Author

peter-csala commented Nov 2, 2023

@martintmk Yeah I do understand that part. Let me try to rephrase my problem.

There is no non-generic Outcome data structure. That's why using ExecuteOutcomeAsync does not neatly work well with async methods.

If there were a non-generic Outcome then you would write something like this

Outcome result = await circuitBreaker.ExecuteOutcomeAsync(static (ctx, state) =>
{
    // Your code goes here

    return ValueTask.FromResult(new Outcome());
}, context, "state");

Anyway maybe overthinking and it is the same problem as with the TaskCompletionSource<TResult>.

@martintmk
Copy link
Contributor

@martintmk Yeah I do understand that part. Let me try to rephrase my problem.

There is no non-generic Outcome data structure. That's why using ExecuteOutcomeAsync does not neatly work well with async methods.

If there were a non-generic Outcome then you would write something like this

Outcome result = await circuitBreaker.ExecuteOutcomeAsync(static (ctx, state) =>
{
    // Your code goes here

    return ValueTask.FromResult(new Outcome());
}, context, "state");

Anyway maybe overthinking and it is the same problem as with the TaskCompletionSource<TResult>.

You are right, there is currently no way to return "void outcome", you need to use workaround you mentioned above. Still, this is for more advanced scenarios so I would wait for feedback until someone really complains :)

@peter-csala
Copy link
Contributor Author

You are right, there is currently no way to return "void outcome", you need to use workaround you mentioned above. Still, this is for more advanced scenarios so I would wait for feedback until someone really complains :)

In this case I will use HttpResponseMessage as TResult to make the example smooth.

@peter-csala peter-csala marked this pull request as ready for review November 2, 2023 09:36
docs/migration-v8.md Outdated Show resolved Hide resolved
docs/migration-v8.md Outdated Show resolved Hide resolved
docs/migration-v8.md Outdated Show resolved Hide resolved
docs/migration-v8.md Outdated Show resolved Hide resolved
docs/strategies/circuit-breaker.md Outdated Show resolved Hide resolved
src/Snippets/Docs/CircuitBreaker.cs Outdated Show resolved Hide resolved
src/Snippets/Docs/CircuitBreaker.cs Outdated Show resolved Hide resolved
src/Snippets/Docs/Migration.CircuitBreaker.cs Outdated Show resolved Hide resolved
src/Snippets/Docs/Migration.CircuitBreaker.cs Outdated Show resolved Hide resolved
src/Snippets/Docs/Migration.CircuitBreaker.cs Outdated Show resolved Hide resolved
@martincostello martincostello merged commit 84236fb into App-vNext:main Nov 2, 2023
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants